/*
 * @name: vehcc
 * @description: Vehicle smart contract for the research on blockchain-based data sharing scheme for Internet of Vehicles.
 * @author: Lulu Zhao
 */

package main

import (
	"encoding/json"
	"fmt"
	"github.com/hyperledger/fabric-chaincode-go/shim"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
)

// SmartContract provides functions for managing a vehicle.
type SmartContract struct {
	contractapi.Contract
}

// Vehicle describes basic details of what makes up a vehicle.
type Vehicle struct {
	VehicleID       string `json:"VehicleID"`
	HashedPassword  string `json:"HashedPassword"`
	CurrentHashTime string `json:"CurrentHashTime"`
	CurrentState    string `json:"CurrentState"`
	Location        string `json:"Location"`
}

// QueryResult structure used for handling result of query.
type QueryResult struct {
	Key    string `json:"Key"`
	Record *Vehicle
}

// ValidateResult structure used for handling result of validation.
type ValidateResult struct {
	CurrentHashTime string `json:"CurrentHashTime"`
	HashedPassword  string `json:"HashedPassword"`
}

// InitLedger adds a base set of vehicles to the ledger.
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	vehicles := []Vehicle{
		Vehicle{VehicleID: "0001", HashedPassword: "123456", CurrentHashTime: "30", CurrentState: "init", Location: "1,1"},
		Vehicle{VehicleID: "0002", HashedPassword: "123456", CurrentHashTime: "30", CurrentState: "init", Location: "1,2"},
		Vehicle{VehicleID: "0003", HashedPassword: "123456", CurrentHashTime: "30", CurrentState: "init", Location: "4,3"},
	}
	for i, vehicle := range vehicles {
		vehicleAsBytes, _ := json.Marshal(vehicle)
		err := ctx.GetStub().PutState(vehicles[i].VehicleID, vehicleAsBytes)
		if err != nil {
			return fmt.Errorf("failed to put to world state %s", err.Error())
		}
	}
	return nil
}

// RegisterVehicle adds a new vehicle to the world state with given details.
func (s *SmartContract) RegisterVehicle(ctx contractapi.TransactionContextInterface, vehicleid string, hashedpassword string, currenthashtime string, currentstate string, location string) error {
	vehicle := Vehicle{
		VehicleID:       vehicleid,
		HashedPassword:  hashedpassword,
		CurrentHashTime: currenthashtime,
		CurrentState:    currentstate,
		Location:        location,
	}
	vehicleAsBytes, _ := json.Marshal(vehicle)
	return ctx.GetStub().PutState(vehicleid, vehicleAsBytes)
}

// QueryVehicle indicates whether the vehicle stored in the world state with given id.
func (s *SmartContract) QueryVehicle(ctx contractapi.TransactionContextInterface, vehicleid string) error {
	vehicleAsBytes, err := ctx.GetStub().GetState(vehicleid)
	if err != nil {
		return fmt.Errorf("failed to read from world state %s", err.Error())
	}
	if vehicleAsBytes == nil {
		return fmt.Errorf("%s does not exist", vehicleid)
	}
	return nil
}

// ValidateVehicle returns the validation information of the vehicle with given id.
func (s *SmartContract) ValidateVehicle(ctx contractapi.TransactionContextInterface, vehicleid string) (*ValidateResult, error) {
	vehicleAsBytes, err := ctx.GetStub().GetState(vehicleid)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state %s", err.Error())
	}
	vehicle := new(Vehicle)
	_ = json.Unmarshal(vehicleAsBytes, vehicle)
	result := new(ValidateResult)
	result.CurrentHashTime = vehicle.CurrentHashTime
	result.HashedPassword = vehicle.HashedPassword
	return result, nil
}

// QueryAllVehicles returns all vehicles found in world state.
func (s *SmartContract) QueryAllVehicles(ctx contractapi.TransactionContextInterface) ([]QueryResult, error) {
	startKey := ""
	endKey := ""
	resultIterator, err := ctx.GetStub().GetStateByRange(startKey, endKey)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state %s", err.Error())
	}
	defer func(resultIterator shim.StateQueryIteratorInterface) {
		err := resultIterator.Close()
		if err != nil {
			_ = fmt.Errorf("failed to close result iterator")
		}
	}(resultIterator)
	var results []QueryResult
	for resultIterator.HasNext() {
		queryResponse, err := resultIterator.Next()
		if err != nil {
			return nil, err
		}
		vehicle := new(Vehicle)
		_ = json.Unmarshal(queryResponse.Value, vehicle)
		queryResult := QueryResult{Key: queryResponse.Key, Record: vehicle}
		results = append(results, queryResult)
	}
	return results, nil
}

// UpdateVehicleState updates the state field of the vehicle with given id.
func (s *SmartContract) UpdateVehicleState(ctx contractapi.TransactionContextInterface, vehicleid string, hashedpassword string, currenthashtime string, currentstate string, location string) error {
	err := s.QueryVehicle(ctx, vehicleid)
	if err != nil {
		return err
	}
	vehicle := new(Vehicle)
	vehicle.VehicleID = vehicleid
	vehicle.HashedPassword = hashedpassword
	vehicle.CurrentHashTime = currenthashtime
	vehicle.CurrentState = currentstate
	vehicle.Location = location
	vehicleAsBytes, _ := json.Marshal(vehicle)
	return ctx.GetStub().PutState(vehicleid, vehicleAsBytes)
}

func main() {
	chaincode, err := contractapi.NewChaincode(new(SmartContract))
	if err != nil {
		fmt.Printf("error create vehcc chaincode: %s", err.Error())
		return
	}
	if err := chaincode.Start(); err != nil {
		fmt.Printf("error starting vehcc chaincode: %s", err.Error())
	}
}
